import hashlib
import math
import time
from typing import Annotated, Optional
from fastapi.encoders import jsonable_encoder
import uvicorn
import datetime
from config import *
from models import *
import jwt
import re
import logging

from fastapi import Depends, FastAPI, Request, status, Response
from fastapi.responses import JSONResponse, PlainTextResponse
from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi.security import APIKeyCookie

import osmnx as ox

### App init

cookie_scheme = APIKeyCookie(name="id", scheme_name="cookie_id", description="Authorize from /login and you will get JWT cookie")

app = FastAPI(
    version="1.0.0",
    title="WeatherForecast API",
    description="API for WeatherForecast",
    root_path=ROOT_PATH,
)
logger = logging.getLogger('uvicorn.error')


@app.on_event("startup")
async def startup():
    # Connect to the database
    db_init = None
    while(not db_init):
        try:
            db_init = PostgresqlDatabase(DB_NAME, user=DB_USER, password=DB_PASSWORD, host=DB_HOST, port=DB_PORT)
        except Exception as e:
            logger.error(f"Error: {e}")
            time.sleep(5)
    logger.info(f"Connected to the database by {type(db)}")
    db.initialize(db_init)
    db.bind(db_models)
    db.connect()
    db.create_tables(db_models)
    test = None
    try:
        test = Account.get_by_id(1)
    except:
        pass
    if not test:
        test = Account.create(firstName="first", lastName="last", email="test@test.test", password=get_hash("testtest"))
        test.save()

### Security functions

def create_jwt(data: dict):
    return jwt.encode({"data": jsonable_encoder(data), "exp": (datetime.datetime.now() + datetime.timedelta(hours=8)).timestamp()}, JWT_KEY, algorithm="HS256")

def decode_jwt(s: str):
    return jwt.decode(s, JWT_KEY, algorithms=["HS256"])

def check_jwt_token(s: str):
    """Check JWT token. If valid then return user data, else return None"""
    try:
        token = decode_jwt(s)
        exp = token.get("exp")
        if exp is None or exp < time.time():
            return None
        return token.get("data")
    except:
        return None

async def get_current_user(token: Annotated[str, Depends(cookie_scheme)]):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials"
    )
    data = check_jwt_token(token)
    if not data or 'id' not in data.keys():
        raise credentials_exception
    user = Account.get_by_id(data["id"])
    if user is None:
        raise credentials_exception
    return user

async def get_current_user_non_strict(token: str):
    data = check_jwt_token(token)
    if not data or 'id' not in data.keys():
        return None
    try:
        user = Account.get_by_id(data["id"])
    except:
        return None
    if user is None:
        return None
    return user

def set_cookie(resp: Response, user: Account):
    resp.set_cookie("id", create_jwt(
                                UserResponse.from_db(
                                    user)
                                )
                            )

def get_hash(password: str):
    return hashlib.sha256(password.encode()).hexdigest()


### Validation error handler

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)

### Account API

@app.post("/register", tags=['auth'], 
          response_model=UserResponse, 
          responses={
              200: {}, 
              201: {"description": "Account created", "model": UserResponse}, 
              400: {"description": "Bad request"}, 
              403: {"description": "Forbidden"}, 
              409: {"description": "Conflict"}
            })
async def register(request: Request, model: RegisterUser):
    """Создание пользователя \n
    POST - /registration \n
    Request:\n
    {  \n
        "firstName": "string", // Имя пользователя 
        "lastName": "string", // Фамилия пользователя
        "email": "string", // Адрес электронной почты
        "password": "string" // Пароль от аккаунта пользователя
    } \n

    Response: \n
    { \n
        "id": "int", // Идентификатор аккаунта пользователя
        "firstName": "string", // Имя пользователя
        "lastName": "string", // Фамилия пользователя
        "email": "string" // Адрес электронной почты
    } \n
 \n
    Условие - Статус \n
    1. Запрос успешно выполнен - 201
    2. firstName = null, firstName = "" или состоит из пробелов, lastName = null, lastName = "" или состоит из пробелов,
    email = null, email = "" или состоит из пробелов, email аккаунта не валидный, password = null, password = "" или состоит из пробелов - 400
    3. Запрос от авторизованного аккаунта - 403
    4. Аккаунт с таким email уже существует - 409
    """
    token = request.cookies.get("id")
    current_user = await get_current_user_non_strict(token)
    print(current_user)
    if current_user:
        raise HTTPException(detail={"message": "Already authorized"}, status_code=403)
    if model.firstName is None or model.firstName.strip() == "":
        raise HTTPException(detail={"message": "Missing first name"}, status_code=400)
    if model.lastName is None or model.lastName.strip() == "":
        raise HTTPException(detail={"message": "Missing last name"}, status_code=400)
    if model.email is None or model.email.strip() == "" or not re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", model.email):
        raise HTTPException(detail={"message": "Missing email"}, status_code=400)
    if model.password is None or model.password.strip() == "":
        raise HTTPException(detail={"message": "Missing password"}, status_code=400)
    user = Account.get_or_none(Account.email == model.email)
    if user:
        raise HTTPException(detail={"message": "Account with this email already exists"}, status_code=409)
    user = Account.create(firstName=model.firstName, lastName=model.lastName, email=model.email, password=get_hash(model.password))
    # return user json with 201 code and set cookie with jwt token
    return JSONResponse(jsonable_encoder(UserResponse.from_db(user=user)), status_code=201)

@app.post("/login", tags=['auth'])
async def login(request: Request, resp: Response, model: LoginUser):
    """Аутентификация пользователя
    POST - /login
    request:
    {
    "email": "string", // Адрес электронной почты
    "password": "string" // Пароль от аккаунта пользователя
    }

    response:
    {
    “id”: "int" // Идентификатор аккаунта пользователя
    }

    Условие - Статус
    Запрос успешно выполнен - 200
    Запрос от авторизованного аккаунта - 200
    Email или пароль не верны - 401
    """
    token = request.cookies.get("id")
    current_user = await get_current_user_non_strict(token)
    if current_user:
        raise HTTPException(detail={"message": "Already authorized"}, status_code=200)
    if model.email is None or model.email.strip() == "" or not re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", model.email):
        raise HTTPException(detail={"message": "Missing email"}, status_code=401)
    if model.password is None or model.password.strip() == "":
        raise HTTPException(detail={"message": "Missing password"}, status_code=401)
    user = Account.get_or_none(Account.email == model.email, Account.password == get_hash(model.password))
    if not user:
        raise HTTPException(detail={"message": "Wrong email or password"}, status_code=401)
    set_cookie(resp, user)
    return UserLoginResponse.from_db(user=user)

@app.get('/accounts/search', tags=['accounts'])
async def search_accounts(firstName: str | None = None, 
                          lastName: str | None = None, 
                          email: str | None = None, 
                          form: int = 0, size: int = 10, 
                          user: Annotated[Account, Depends(get_current_user)] = None):
    """Поиск аккаунта
    \n
    GET - /accounts/search\n
    ?firstName={firstName} &lastName={lastName} &email={email} &form={form} &size={size} \n
    {firstName}: "string", // Имя пользователя, может использоваться только часть имени без учета регистра, если null, не участвует в фильтрации\n
    {firstName}: "string", // Фамилия пользователя, может использоваться только часть фамилии без учета регистра, если null, не участвует в фильтрации\n
    {email}: "string", // Адрес электронной почты, может использоваться только часть адреса электронной почты без учета регистра, если null, не участвует в фильтрации\n
    {form}: "int" // Количество элементов, которое необходимо пропустить для формирования страницы с результатами (по умолчанию 0)\n
    {size}: "int" // Количество элементов на странице (по умолчанию 10)\n
    request 
    { \n
        empty
    }\n
    response
    [\n
        {\n
            “id”: "int", // Идентификатор аккаунта пользователя
            "firstName": "string", // Имя пользователя
            "lastName": "string", // Фамилия пользователя
            "email": "string" // Адрес электронной почты
        }\n
    ]\n
    Результаты поиска сортируются по id аккаунта от наименьшего к наибольшему
    \n\n
    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. form < 0, size <= 0 - 400
    3. Неверные авторизационные данные - 401
    """
    if form < 0 or size <= 0:
        raise HTTPException(detail={"message": "Invalid form or size"}, status_code=400)
    users = Account.select()
    if firstName:
        users = users.where(Account.firstName.contains(firstName))
    if lastName:
        users = users.where(Account.lastName.contains(lastName))
    if email:
        users = users.where(Account.email.contains(email))
    users = users.order_by(Account.id).paginate(form, size)
    return [UserResponse.from_db(user=user) for user in users]


@app.get("/accounts/{accountId}", tags=['accounts'])
async def get_account(req: Request, accountId: int,
                      user: Annotated[Account, Depends(get_current_user)] = None):
    """
    Аккаунт пользователя
    GET - /accounts/{accountId}
    Получение аккаунта {accountId}: "int" // Идентификатор аккаунта пользователя
    request 
    { 
        empty
    }

    response
    {
    "id": "int", // Идентификатор аккаунта пользователя
    "firstName": "string", // Имя пользователя
    "lastName": "string", // Фамилия пользователя
    "email": "string" // Адрес электронной почты
    }

    Условие - Статус
    Запрос успешно выполнен - 200
    accountId = null, accountId <= 0 - 400
    Неверные авторизационные данные - 401
    Аккаунт с таким accountId не найден - 404
    """
    if accountId is None or accountId <= 0:
        raise HTTPException(detail={"message": "Missing account id"}, status_code=400)
    user = Account.get_or_none(Account.id == accountId)
    if not user:
        raise HTTPException(detail={"message": "Account not found"}, status_code=404)
    return UserResponse.from_db(user=user)


@app.put('/accounts/{accountId}', tags=['accounts'])
async def updateAccount(req: Request, accountId: int, model: RegisterUser,
                      user: Annotated[Account, Depends(get_current_user)] = None):
    """
    Изменение аккаунта PUT - /accounts/{accountId}\n
    accountId: "int" // Идентификатор аккаунта пользователя\n
    request
    {\n
        "firstName": "string", // Новое имя пользователя
        "lastName": "string", // Новая фамилия пользователя
        "email": "string", // Новый адрес электронной почты
        "password": "string" // Пароль от аккаунта
    }\n

    response
    {\n
        "id": "int", // Идентификатор аккаунта пользователя
        "firstName": "string", // Новое имя пользователя
        "lastName": "string", // Новая фамилия пользователя
        "email": "string" // Новый адрес электронной почты
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. accountId = null, accountId <= 0, firstName = null, firstName = "" или состоит из пробелов, lastName = null, 
    lastName = "" или состоит из пробелов, email = null, email = "" или состоит из пробелов, email аккаунта не валидный, 
    password = null, password = "" или состоит из пробелов - 400
    3. Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    4. Обновление не своего аккаунта, Аккаунт не найден - 403
    5. Аккаунт с таким email уже существует - 409
    """
    if accountId is None or accountId <= 0:
        raise HTTPException(detail={"message": "Missing account id"}, status_code=400)
    if accountId != user.id:
        raise HTTPException(detail={"message": "You can't update other accounts"}, status_code=403)
    if model.firstName is None or model.firstName.strip() == "":
        raise HTTPException(detail={"message": "Missing first name"}, status_code=400)
    if model.lastName is None or model.lastName.strip() == "":
        raise HTTPException(detail={"message": "Missing last name"}, status_code=400)
    if model.email is None or model.email.strip() == "" or not re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", model.email):
        raise HTTPException(detail={"message": "Missing or not valid email"}, status_code=400)
    if model.password is None or model.password.strip() == "":
        raise HTTPException(detail={"message": "Missing password"}, status_code=400)
    if Account.get_or_none(Account.email == model.email, Account.id != accountId):
        raise HTTPException(detail={"message": "Account with this email already exists"}, status_code=409)
    user.firstName = model.firstName
    user.lastName = model.lastName
    user.email = model.email
    user.password = get_hash(model.password)
    user.save()
    set_cookie(req, user)
    return UserResponse.from_db(user)

@app.delete('/accounts/{accountId}', tags=['accounts'])
def deleteAccount(req: Request, accountId: int,
                      user: Annotated[Account, Depends(get_current_user)] = None):
    """
    DELETE - /accounts/{accountId}
    Удаление аккаунта {accountId}: "int" // Идентификатор аккаунта пользователя

    request { empty
    }

    response { empty
    }

    Условие - Статус
    Запрос успешно выполнен - 200
    accountId = null, accountId <= 0 - 400
    Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    Удаление не своего аккаунта, Аккаунт с таким accountId не найден - 403
    """
    if accountId is None or accountId <= 0:
        raise HTTPException(detail={"message": "Missing account id"}, status_code=400)
    if accountId != user.id:
        raise HTTPException(detail={"message": "You can't delete other accounts"}, status_code=403)
    user.delete_instance()
    return {}

### Region API

@app.get("/region/{regionId}", tags=['region'])
def get_region(regionId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Регион
    В МЕТОДАХ РЕГИОНА ИСПОЛЬЗУЕТСЯ НАИМЕНОВАНИЕ РОДИТЕЛЬСКОГО РЕГИОНА КАК КЛЮЧ, ПОЭТОМУ НАЗВАНИЯ РЕГИОНОВ ДОЛЖНЫ БЫТЬ УНИКАЛЬНЫМИ\n 
    GET /region/{regionld}\n
    Получение информации о регионе {regionld} = "long" // идентификатор региона\n
    request \n
    { \n
    empty 
    }\n
    response \n
    { 
        id: "long", // Идентификатор региона
        "regionType": "[long]", //Массив идентификаторов типа региона (откуда, если мы их больше нигде не присваиваем?)
        "accountld": "long" //Аккаунт внесший данные о регионе,  
        "name": "string", // Название региона 
        "parentRegion": "string", // Название родительского региона
        "latitude": "double", // Координаты широты 
        "longitude": "double", // Координаты долготы 
        "osmData": {
            "mapLink": "string", // Ссылка на карту
            "boundingBox": ["double",", "double", "double", "double"], // Границы региона
            "pointsOfInterest": [
                {
                    "name": "string", // Название точки интереса
                    "type": "string", // Тип точки интереса
                    "latitude": "double", // Широта
                    "longitude": "double" // Долгота
                }
            ]
        }
    } \n\n
    ПРОШУ ЗАМЕТИТЬ, ЧТО ДАННЫЙ МЕТОД ЕДИНСТВЕННЫЙ, У КОТОРОГО В ОТВЕТЕ УКАЗЫВАЕТСЯ НОМЕР АККАУНТА, ТАК КАК ЭТОТ ПУНКТ ЕСТЬ В ТЗ\n
    Условие - Статус
    1. Запрос успешно выполнен - 200
    2. regionld = null, regionld <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Регион с таким regionld не найдена - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    region = Region.get_or_none(Region.id == regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    return RegionGetResponse.from_db(region)

def get_pois(lat: float, lon: float, lat1: float, lon1: float):
    """Get data from OpenStreetMap
    lat: float - latitude
    lon: float - longitude
    include_poi: bool - include points of interest
    return: dict - data from OpenStreetMap
    """
    try:
        pois = ox.features_from_bbox(bbox=(lon, lat, lon1, lat1), tags={"tourism": "*", "amenity": ['restaurant', 'bar', 'cafe']})
        pois_obj = []
        for poi in pois.iloc:
            try:
                pois_obj.append(POI(name=poi.get("name"), type=poi.get("amenity"), latitude=poi.get("geometry").x, longitude=poi.get("geometry").y))
            except:
                pass
        pois = pois_obj
    except:
        pois = []
    return pois

@app.post("/region", tags=['region'])
def new_region(req: Request, model: RegionRequest,
                      user: Annotated[Account, Depends(get_current_user)] = None):
    """Добавление региона\n
    POST - /region\n
    request { \n
        "name": "string", // Название региона
        "parentRegion": "string" | "Null", // Название родительского региона
        //"regionType": "long", //Идентификатор типа региона (зачем убрали то...)
        "latitude": "double", // Координаты широты 
        "longitude": double", // Координаты долготы
        "latitude1": "double", // Координаты широты 
        "longitude1": double", // Координаты долготы
        "osmData": { // Необязательный. Данные для интеграции с OpenStreetMap
            "includePOI": "boolean" // Указывает, следует ли включать точки интереса из OSM. True для
                                    //включения, False или отсутствует для игнорирования.
        }
    }
    responce { \n
        "id": "long" // Идентификатор региона 
        "name": "string", // Название региона 
        "parentRegion": "string", // Название родительского региона
        //"regionType": "long", //Идентификатор типа региона 
        "latitude": "double", // Координаты широты 
        "longitude": double", // Координаты долготы
        "osmData": { // Данные из OpenStreetMap, если было запрошено и успешно получено
            "mapLink": "string", // Ссылка на карту региона в OSM
            "boundingBox": ["double", "double", "double", "double"], // Границы региона
            "pointsOfInterest": [{
                "name": "string", // Название точки интереса
                "type": "string", // Тип точки интереса
                "latitude": "double", // Широта
                "longitude": "double" // Долгота
            }]
        }
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 201
    2. latitude = null, name = null, longitude = null - 400
    3. Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    4. Регион с такими latitude и longitude уже существует - 409
    """
    if model.latitude is None or model.longitude is None or model.name is None:
        raise HTTPException(detail={"message": "Missing latitude, longitude or name"}, status_code=400)
    region = Region.get_or_none(Region.longitude == model.longitude, 
                                Region.latitude == model.latitude)
    if region:
        raise HTTPException(detail={"message": "Region with this latitude and longitude already exists"}, status_code=409)
    parentRegion = Region.get_or_none(Region.name == model.parentRegion)
    if model.parentRegion and model.parentRegion.strip() != "":
        if not parentRegion:
            raise HTTPException(detail={"message": "Parent region not found"}, status_code=404)
    if model.regionType:
        regionType = RegionType.get_or_none(RegionType.id == model.regionType)
    else:
        regionType = None
    # if not regionType:
    #     raise HTTPException(detail={"message": "Region type not found"}, status_code=404)
    try:
        if(model.osmData.includePOI):
            pois = get_pois(model.latitude, model.longitude, model.latitude1, model.longitude1)
        else:
            pois = []
        osm = OsmData.create(mapLink=f"https://www.openstreetmap.org/export/embed.html?bbox={model.longitude},{model.latitude},{model.longitude1},{model.latitude1}", 
                             boundingBox_x1=model.latitude, boundingBox_y1=model.longitude, boundingBox_x2=model.latitude1, boundingBox_y2=model.longitude1,
                                pointsOfInterest=pois)
        region = Region.create(name=model.name, parentRegion=parentRegion, regionType=regionType, 
                           latitude=model.latitude, longitude=model.longitude, accountId=user.id, osmData=osm)
    except IntegrityError:
        raise HTTPException(detail={"message": "Region with this name already exists"}, status_code=409)
    region.save()
    return RegionGetResponse.from_db(region)

@app.put("/region/{regionId}", tags=['region'])
def update_region(regionId: int, model: RegionRequest,
                user: Annotated[Account, Depends(get_current_user)] = None) -> RegionGetResponse:
    """Изменение региона\n
    PUT - /region/{regionId}\n
    request { \n
        "name": "string", // Название региона
        "parentRegion": "string", // Название родительского региона
        //"regionType": "long", //Идентификатор типа региона 
        "latitude": "double", // Координаты широты 
        "longitude": double", // Координаты долготы
        "latitude1": "double", // Координаты широты 
        "longitude1": double", // Координаты долготы
        "osmData": { // Необязательный. Данные для запроса обновления информации из OpenStreetMap
            "updatePOI": "boolean" // Указывает на необходимость
            //обновления точек интереса из OSM. True для обновления, False
            //или отсутствует для игнорирования.
        }
    }\n
    response { \n
        "id": "long" // Идентификатор региона 
        "name": "string", // Название региона 
        "parentRegion": "string", // Название родительского региона
        "latitude": "double", // Координаты широты 
        "longitude": double", // Координаты долготы
        "osmData": { // Обновленные данные из OpenStreetMap, если было запрошено и успешно получено
            "mapLink": "string", // Ссылка на карту региона в OSM
            "boundingBox": ["double", "double", "double", "double"], // Границы региона
            "pointsOfInterest": [{
                "name": "string", // Название точки интереса
                "type": "string", // Тип точки интереса
                "latitude": "double", // Широта
                "longitude": "double" // Долгота
            }]
        }
    }\n\n
    ПРОШУ ЗАМЕТИТЬ, ЧТО В ОТВЕТЕ НЕ УКАЗЫВАЕТСЯ ТИП РЕГИОНА, ТАК КАК ЭТОГО ПУНКТА НЕТ В ТЗ\n
    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. regionld = null, name = null, latitude = null, longitude = null - 400
    3. Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    4. Регион с такими latitude и longitude уже существует - 409
    5. Регион с таким regionId не найден - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    if model.latitude is None or model.longitude is None or model.name is None:
        raise HTTPException(detail={"message": "Missing latitude, longitude or name"}, status_code=400)
    if model.latitude1 is None or model.longitude1 is None:
        raise HTTPException(detail={"message": "Missing latitude1, longitude1"}, status_code=400)
    region = Region.get_or_none(Region.id == regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    if model.osmData.updatePOI:
        pois = get_pois(model.latitude, model.longitude, model.latitude1, model.longitude1)
        osm = OsmData(mapLink=f"https://www.openstreetmap.org/export/embed.html?bbox={model.longitude},{model.latitude},{model.longitude1},{model.latitude1}",
                      boundingBox_x1=model.latitude, boundingBox_y1=model.longitude, boundingBox_x2=model.latitude1, boundingBox_y2=model.longitude1,
                      pointsOfInterest=pois)
        osm.save()
        region.osmData = osm
    parentRegion = Region.get_or_none(Region.name == model.parentRegion)
    region.name = model.name
    region.parentRegion = parentRegion
    # region.regionType = model.regionType
    region.latitude = model.latitude
    region.longitude = model.longitude
    region.save()
    return RegionGetResponse.from_db(region)

@app.delete("/region/{regionId}", tags=['region'])
def delete_region(regionId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Удаление региона\n
    DELETE - /region/{regionId}\n
    {regionId}: "long" // Идентификатор региона\n
    request { \n
        empty
    }\n
    response { \n
        empty
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. regionId = null, regionId <= 0 - 400
    3. Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    4. Регион с таким regionId не найден - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    region = Region.get_or_none(Region.id == regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    if len(region.subregions) > 0:
        raise HTTPException(detail={"message": "Can't delete parent region that has children"}, status_code=400)
    region.delete_instance()
    return {}

@app.get("/region/types/{typeId}", tags=['regionType'])
def get_region_type(typeId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Тип региона\n
    GET /region/types/{typeId}\n
    Получение информации о типе региона {typeId}: "long" // Идентификатор типа региона\n
    request { \n
        empty
    }\n
    response { \n
        "id": "long", // Идентификатор типа региона
        "type": "string" // Тип региона
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. typeId = null, typeId <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Тип региона с таким typeId не найден - 404
    """
    if typeId is None or typeId <= 0:
        raise HTTPException(detail={"message": "Missing type id"}, status_code=400)
    regionType = RegionType.get_or_none(RegionType.id == typeId)
    if not regionType:
        raise HTTPException(detail={"message": "Region type not found"}, status_code=404)
    return RegionTypeResponse.from_db(regionType)

@app.post("/region/types", tags=['regionType'])
def new_region_type(model: RegionTypeRequest,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Добавление типа региона\n
    POST - /region/types\n
    request { \n
        "type": "string" // Тип региона
    }\n
    response { \n
        "id": "long", // Идентификатор типа региона
        "type": "string" // Тип региона
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 201
    2. type = null, type = "" или состоит из пробелов - 400
    3. Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    4. Тип региона с таким type уже существует - 409
    """
    if model.type is None or model.type.strip() == "":
        raise HTTPException(detail={"message": "Missing type"}, status_code=400)
    regionType = RegionType.get_or_none(RegionType.type == model.type)
    if regionType:
        raise HTTPException(detail={"message": "Region type with this type already exists"}, status_code=409)
    regionType = RegionType.create(type=model.type)
    return RegionTypeResponse.from_db(regionType)

@app.put("/region/types/{typeId}", tags=['regionType'])
def update_region_type(typeId: int, model: RegionTypeRequest,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Изменение типа региона\n
    PUT - /region/types/{typeId}\n
    {typeld}: "long" //Идентификатор типа региона
    request { \n
        "type": "string" // Тип региона
    }\n
    response { \n
        "id": "long", // Идентификатор типа региона
        "type": "string" // Тип региона
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. typeId = null, typeId <= 0, type = null, type = "" или состоит из пробелов - 400
    3. Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    4. Тип региона с таким type уже существует - 409
    5. Тип региона с таким typeId не найден - 404
    """
    if typeId is None or typeId <= 0:
        raise HTTPException(detail={"message": "Missing type id"}, status_code=400)
    if model.type is None or model.type.strip() == "":
        raise HTTPException(detail={"message": "Missing type"}, status_code=400)
    regionType = RegionType.get_or_none(RegionType.id == typeId)
    if not regionType:
        raise HTTPException(detail={"message": "Region type not found"}, status_code=404)
    regionType.type = model.type
    try:
        regionType.save()
    except IntegrityError:
        raise HTTPException(detail={"message": "Region type with this type already exists"}, status_code=409)
    return RegionTypeResponse.from_db(regionType)

@app.delete("/region/types/{typeId}", tags=['regionType'])
def delete_region_type(typeId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Удаление типа региона\n
    DELETE - /region/types/{typeId}\n
    {typeId}: "long" // Идентификатор типа региона\n
    request { \n
        empty
    }\n
    response { \n
        empty
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. typeId = null, typeId <= 0, есть регионы с данным типом - 400
    3. Запрос от неавторизованного аккаунта, Неверные авторизационные данные - 401
    4. Тип региона с таким typeId не найден - 404
    """
    if typeId is None or typeId <= 0:
        raise HTTPException(detail={"message": "Missing type id"}, status_code=400)
    regionType = RegionType.get_or_none(RegionType.id == typeId)
    if not regionType:
        raise HTTPException(detail={"message": "Region type not found"}, status_code=404)
    if len(regionType.regions) > 0:
        raise HTTPException(detail={"message": "Can't delete region type that has regions"}, status_code=400)
    regionType.delete_instance()
    return {}

@app.get("/region/weather/search", tags=['regionWeather'])
def search_weather(startDateTime: Optional[datetime.datetime] = None, 
                   endDateTime: Optional[datetime.datetime] = None, regionId: Optional[int] = None, 
                   weatherCondition: Optional[WeatherCondition] = None, 
                   form: int = 0, size: int = 10,
                   user: Annotated[Account, Depends(get_current_user)] = None):
    """Поиск погоды\n
    GET /region/weather/search\n
    ?startDateTime={startDateTime} &endDateTime={endDateTime} &regionId={regionId} &weatherCondition={weatherCondition} &form={form} &size={size}\n
    {startDateTime}: "dateTime" // Дата и время начала периода поиска в формате ISO-8601\n
    {endDateTime}: "dateTime" // Дата и время окончания периода поиска в формате ISO-8601\n
    {regionId}: "long" // Идентификатор региона\n
    {weatherCondition}: "string" // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM\n
    {form}: "int" // Количество элементов, которое необходимо пропустить для формирования страницы с результатами (по умолчанию 0)\n
    {size}: "int" // Количество элементов на странице (по умолчанию 10)\n

    request { \n
        empty
    }\n

    response [\n
        { \n
            "id": "long", // Идентификатор региона
            "regionName": "string", // Название региона
            "temperature": "float", // Температура
            "humidity": "float", // Влажность, %
            "windSpeed": "float", // Скорость ветра, м/с
            "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
            "precipitationAmount": "float", // Количество осадков, мм 
            "measurementDateTime": "dateTime" //Дата и время измерения погодных условий в формате ISO-8601 
            "weatherForecast": [forecastld], //Массив объектов с прогнозом погоды на ближайшие дни
        }\n
    ]\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. windSpeed < 0, measurementDateTime - не в формате ISO-8601, 
    weatherCondition != "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM",
    precipitationAmount < 0 regionld <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Регион с regionld не найден или forecastld в данном регионе не найдена - 404
    """
    weathers = Weather.select()
    if startDateTime:
        weathers = weathers.where(Weather.measurementDateTime >= startDateTime)
    if endDateTime:
        weathers = weathers.where(Weather.measurementDateTime <= endDateTime)
    if regionId:
        weathers = weathers.where(Weather.regionId == regionId)
    if weatherCondition:
        weathers = weathers.where(Weather.weatherCondition == weatherCondition.value)
    weathers = weathers.order_by(Weather.measurementDateTime).paginate(form, size)
    return [WeatherResponse.from_db(weather) for weather in weathers]

@app.get("/region/weather/{regionId}", tags=['regionWeather'])
def get_weather(regionId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Погода\n
    GET /region/weather/{regionId}\n
    Получение информации о погоде в регионе \n
    {regionId}: "long" // Идентификатор региона\n
    request { \n
        empty
    }\n
    response { \n
        "id": "long", // Идентификатор региона
        "regionName": "string", // Название региона
        "temperature": "float", // Температура
        "humidity": "float", // Влажность, %
        "windSpeed": "float", // Скорость ветра, м/с
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
        "precipitationAmount": "float", // Количество осадков, мм 
        "measurementDateTime": "dateTime" //Дата и время измерения погодных условий в формате ISO-8601 
        "weatherForecast": [
            {
                "forecastId": "long", // Идентификатор прогноза погоды
                "dateTime": "dateTime", // Дата и время прогноза
                "temperature": "float", // Прогнозируемая температура, °C
                "weatherCondition": "string", // Прогнозируемое состояние погоды
                "precipitationAmount": "float", // Прогнозируемое количество осадков, мм
                "windSpeed": "float" // Прогнозируемая скорость ветра, м/с
            }
        ],
        "osmLink": "string" // Ссылка на карту региона в OpenStreetMap для просмотра географического положения
    }\n\n

    Условие - Статус\n
    1. Запрос успешно выполнен - 200
    2. regionId = null, regionId <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Регион с таким regionId не найден или ни один forecastId для этого региона не найден - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    region = Region.get_or_none(Region.id == regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    weather = Weather.get_or_none(Weather.regionId == regionId)
    if not weather:
        raise HTTPException(detail={"message": "Weather not found"}, status_code=404)
    forecasts = Forecast.select().where(Forecast.regionId == regionId).order_by(Forecast.dateTime)
    if not forecasts:
        raise HTTPException(detail={"message": "Forecasts not found"}, status_code=404)
    return WeatherResponse.from_db(weather)

@app.post("/region/weather", tags=['regionWeather'])
def add_weather(model: WeatherRequest,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Добавление погоды\n
    POST - /region/weather\n
    request {\n 
        "regionId": "long", // Идентификатор региона 
        "temperature": "float", // Температура в регионе, °C 
        "humidity": "float", // Влажность воздуха в регионе, % 
        "windSpeed": "float", // Скорость ветра, м/с 
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM" 
        "precipitationAmount": "float", // Количество осадков, мм 
        "measurementDateTime": "dateTime", //Дата и время измерения погодных условий в формате ISO-8601 
        "weatherForecast": ["forecastId"]// Массив идентификаторов объектов с прогнозом погоды на ближайшие дни 
    } 
    responce {\n 
        "id": "long", // Уникальный идентификатор созданной записи о погоде в регионе
        "temperature": "float", // Температура в регионе, °C 
        "humidity": "float", / Влажность воздуха в регионе, % 
        "windSpeed": "float", // Скорость ветра, м/с 
        "weatherCondition": "string", // Текущее состояние погоды 
        "precipitationAmount": "float", // Количество осадков, мм 
        "measurementDateTime": "dateTime", // Дата и время измерения погодных условий 
        "weatherForecast": ["forecastId"], // Массив идентификаторов объектов с прогнозом погоды на ближайшие дни 
    }\n
    ПРОШУ ОБРАТИТЬ ВНИМАНИЕ, ЧТО В ТЗ В ОТВЕТЕ НА ЗАПРОС НЕ УКАЗАН РЕГИОН, ПОЭТОМУ ЕГО ЗДЕСЬ НЕТ
    \n
    Условие - Статус
    1. Запрос успешно выполнен - 200
    2. regionld = null, measurementDateTime - не в формате ISO-8601, windSpeed < 0
    weatherCondition != "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM"
    precipitationAmount < 0 regionld <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Регион с regionld не найден или forecastId в данном регионе не найдена - 404
    """
    if model.regionId is None or model.regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    if model.measurementDateTime is None:
        raise HTTPException(detail={"message": "Missing measurement date time"}, status_code=400)
    if model.windSpeed < 0:
        raise HTTPException(detail={"message": "Wind speed can't be negative"}, status_code=400)
    if model.weatherCondition not in WeatherCondition:
        raise HTTPException(detail={"message": "Invalid weather condition"}, status_code=400)
    if model.precipitationAmount < 0:
        raise HTTPException(detail={"message": "Precipitation amount can't be negative"}, status_code=400)
    region = Region.get_or_none(Region.id == model.regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    forecasts = []
    for forecastId in model.weatherForecast:
        forecast = Forecast.get_or_none(Forecast.id == forecastId)
        if not forecast:
            raise HTTPException(detail={"message": "Forecast not found"}, status_code=404)
        forecasts.append(forecast)
    weather = Weather.create(regionId=model.regionId, 
                             temperature=model.temperature, 
                             humidity=model.humidity, 
                             windSpeed=model.windSpeed, 
                             weatherCondition=model.weatherCondition, 
                             precipitationAmount=model.precipitationAmount, 
                             measurementDateTime=model.measurementDateTime)
    weather.forecasts = forecasts
    weather.save()
    return WeatherPostResponse.from_db(weather)

@app.put("/region/weather/{regionId}", tags=['regionWeather'])
def update_weather(regionId: int, model: WeatherRequest,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Изменение погоды\n
    PUT - /region/weather/{regionId}\n
    {regionId}: "long" // Идентификатор погоды\n
    request {\n 
        "regionId": "long", // Идентификатор региона 
        "temperature": "float", // Температура в регионе, °C 
        "humidity": "float", // Влажность воздуха в регионе, % 
        "windSpeed": "float", // Скорость ветра, м/с 
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM" 
        "precipitationAmount": "float", // Количество осадков, мм 
        "measurementDateTime": "dateTime", //Дата и время измерения погодных условий в формате ISO-8601 
        "weatherForecast": ["forecastId"]// Массив идентификаторов объектов с прогнозом погоды на ближайшие дни 
    } 
    responce {\n 
        "id": "long", // Уникальный идентификатор созданной записи о погоде в регионеа 
        "temperature": "float", // Температура в регионе, °C 
        "humidity": "float", / Влажность воздуха в регионе, % 
        "windSpeed": "float", // Скорость ветра, м/с 
        "weatherCondition": "string", // Текущее состояние погоды 
        "precipitationAmount": "float", // Количество осадков, мм 
        "measurementDateTime": "dateTime", // Дата и время измерения погодных условий 
        "weatherForecast": ["forecastId"], // Массив идентификаторов объектов с прогнозом погоды на ближайшие дни
    }\n\n
    Условие - Статус
    1. Запрос успешно выполнен - 200
    2. regionld = null, measurementDateTime - не в формате ISO-8601, windSpeed < 0
    weatherCondition != "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM"
    precipitationAmount < 0 regionld <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Регион с regionld не найден или forecastId в данном регионе не найдена - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    if model.measurementDateTime is None:
        raise HTTPException(detail={"message": "Missing measurement date time"}, status_code=400)
    if model.windSpeed < 0:
        raise HTTPException(detail={"message": "Wind speed can't be negative"}, status_code=400)
    if model.weatherCondition not in WeatherCondition:
        raise HTTPException(detail={"message": "Invalid weather condition"}, status_code=400)
    if model.precipitationAmount < 0:
        raise HTTPException(detail={"message": "Precipitation amount can't be negative"}, status_code=400)
    region = Region.get_or_none(Region.id == model.regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    weather = Weather.get_or_none(Weather.regionId == regionId)
    if not weather:
        raise HTTPException(detail={"message": "Weather not found"}, status_code=404)
    forecasts = []
    for forecastId in model.weatherForecast:
        forecast = Forecast.get_or_none(Forecast.id == forecastId)
        if not forecast:
            raise HTTPException(detail={"message": "Forecast not found"}, status_code=404)
        forecasts.append(forecast)
    weather.regionId = model.regionId
    weather.temperature = model.temperature
    weather.humidity = model.humidity
    weather.windSpeed = model.windSpeed
    weather.weatherCondition = model.weatherCondition
    weather.precipitationAmount = model.precipitationAmount
    weather.measurementDateTime = model.measurementDateTime
    weather.save()
    weather.forecasts.clear()
    for forecast in forecasts:
        weather.forecasts.add(forecast)
    return WeatherResponse.from_db(weather)

@app.delete("/region/weather/{regionId}", tags=['regionWeather'])
def delete_weather(regionId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Удаление погоды\n
    DELETE - /region/weather/{regionId}\n
    {regionId}: "long" // Идентификатор погоды\n
    request { \n
        empty
    }\n
    response { \n
        empty
    }\n
    В ТЗ НЕПОНЯТНО ОДНА ЛИ ПОГОДА У РЕГИОНА, ПОЭТОМУ В ДАННОМ СЛУЧАЕ УДАЛЯЮТСЯ ВСЕ ПОГОДЫ, У КОТОРЫХ REGIONID == REGIONID ИЗ ЗАПРОСА 
    \n
    Условие - Статус
    1. Запрос успешно выполнен - 200
    2. regionId = null, regionId <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Погода с regionId не найдена - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    weathers = Weather.select().where(Weather.regionId == regionId)
    if len(weathers) == 0:
        raise HTTPException(detail={"message": "Weather not found"}, status_code=404)
    for i in weathers:
        i.delete_instance()
    return {}

@app.get("/region/weather/forecast/{forecastId}", tags=['regionWeatherForecast'])
def get_forecast(forecastId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """
    Получение прогноза погоды\n
    GET /region/weather/forecast/{forecastId}\n
    {forecastId}: "long" // Идентификатор прогноза погоды\n
    request { \n
        empty
    }\n
    response { \n
        "id": "long", // Идентификатор прогноза погоды
        "regionId": "long", // Идентификатор региона
        "temperature": "float", // Температура
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
        "dateTime": "dateTime" //Дата и время прогноза в формате ISO-8601
    }\n\n
    """
    if forecastId is None or forecastId <= 0:
        raise HTTPException(detail={"message": "Missing forecast id"}, status_code=400)
    forecast = Forecast.get_or_none(Forecast.id == forecastId)
    if not forecast:
        raise HTTPException(detail={"message": "Forecast not found"}, status_code=404)
    return ForecastResponse.from_db(forecast)

@app.put("/region/weather/forecast/{forecastId}", tags=['regionWeatherForecast'])
def update_forecast(forecastId: int, model: ForecastRequest,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """
    Изменение прогноза погоды\n
    PUT - /region/weather/forecast/{forecastId}\n
    {forecastId}: "long" // Идентификатор прогноза погоды\n
    request { \n
        "temperature": "float", // Температура
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
        "dateTime": "dateTime" //Дата и время прогноза в формате ISO-8601
    }\n
    response { \n
        "id": "long", // Идентификатор прогноза погоды
        "regionId": "long", // Идентификатор региона
        "temperature": "float", // Температура
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
        "dateTime": "dateTime" //Дата и время прогноза в формате ISO-8601
    }\n\n
    """
    if forecastId is None or forecastId <= 0:
        raise HTTPException(detail={"message": "Missing forecast id"}, status_code=400)
    if model.dateTime is None:
        raise HTTPException(detail={"message": "Missing date time"}, status_code=400)
    if model.weatherCondition not in WeatherCondition:
        raise HTTPException(detail={"message": "Invalid weather condition"}, status_code=400)
    forecast = Forecast.get_or_none(Forecast.id == forecastId)
    if not forecast:
        raise HTTPException(detail={"message": "Forecast not found"}, status_code=404)
    forecast.temperature = model.temperature
    forecast.weatherCondition = model.weatherCondition
    forecast.dateTime = model.dateTime
    forecast.save()
    return ForecastResponse.from_db(forecast)

@app.post("/region/weather/forecast", tags=['regionWeatherForecast'])
def new_forecast(model: ForecastRequest,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """
    Добавление прогноза погоды\n
    POST - /region/weather/forecast\n
    request { \n
        "regionId": "long", // Идентификатор региона
        "temperature": "float", // Температура
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
        "dateTime": "dateTime" //Дата и время прогноза в формате ISO-8601
    }\n
    response { \n
        "id": "long", // Идентификатор прогноза погоды
        "regionId": "long", // Идентификатор региона
        "temperature": "float", // Температура
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
        "dateTime": "dateTime" //Дата и время прогноза в формате ISO-8601
    }\n\n
    """
    if model.dateTime is None:
        raise HTTPException(detail={"message": "Missing date time"}, status_code=400)
    if model.weatherCondition not in WeatherCondition:
        raise HTTPException(detail={"message": "Invalid weather condition"}, status_code=400)
    region = Region.get_or_none(Region.id == model.regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    forecast = Forecast.create(regionId=model.regionId, 
                               temperature=model.temperature, 
                               weatherCondition=model.weatherCondition, 
                               dateTime=model.dateTime)
    return ForecastResponse.from_db(forecast)

@app.delete("/region/weather/forecast/{forecastId}", tags=['regionWeatherForecast'])
def delete_forecast(forecastId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """
    Удаление прогноза погоды\n
    DELETE - /region/weather/forecast/{forecastId}\n
    {forecastId}: "long" // Идентификатор прогноза погоды\n
    request { \n
        empty
    }\n
    response { \n
        empty
    }\n\n
    """
    if forecastId is None or forecastId <= 0:
        raise HTTPException(detail={"message": "Missing forecast id"}, status_code=400)
    forecast = Forecast.get_or_none(Forecast.id == forecastId)
    if not forecast:
        raise HTTPException(detail={"message": "Forecast not found"}, status_code=404)
    forecast.delete_instance()
    return {}


@app.post("/region/{regionId}/weather/{weatherId}", tags=["regionWeather"])
def add_weather_for_region(regionId: int, weatherId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Добавление погоды для региона\n
    POST - /region/{regionId}/weather/{weatherId}\n
    {regionId}: "long" // Идентификатор региона\n
    {weatherId}: "long" // Идентификатор погоды\n
    request { \n
        empty
    }\n
    response { \n
        "id": "long", // Идентификатор региона
        "regionName": "string", // Название региона
        "temperature": "float", // Температура
        "humidity": "float", // Влажность, %
        "windSpeed": "float", // Скорость ветра, м/с
        "weatherCondition": "string", // Текущее состояние погоды, доступные значения "CLEAR", "CLOUDY", "RAIN", "SNOW", "FOG", "STORM
        "precipitationAmount": "float", // Количество осадков, мм
        "measurementDateTime": "dateTime" //Дата и время измерения погодных условий в формате ISO-8601
        "weatherForecast": [forecastld], //Массив объектов с прогнозом погоды на ближайшие дни
    }\n\n
    Условие - Статус
    1. Запрос успешно выполнен - 200
    2. regionId = null, regionId <= 0, weatherId = null, weatherId <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Регион с regionId не найден или погода с weatherId не найдена - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    if weatherId is None or weatherId <= 0:
        raise HTTPException(detail={"message": "Missing weather id"}, status_code=400)
    region = Region.get_or_none(Region.id == regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    weather = Weather.get_or_none(Weather.id == weatherId)
    if not weather:
        raise HTTPException(detail={"message": "Weather not found"}, status_code=404)
    weather.regionId = regionId
    weather.save()
    return AssignWeatherToRegionResponse.from_db(weather)

@app.delete("/region/{regionId}/weather/{weatherId}", tags=["regionWeather"])
def disassign_weather_for_region(regionId: int, weatherId: int,
                user: Annotated[Account, Depends(get_current_user)] = None):
    """Удаление погоды для региона\n
    DELETE - /region/{regionId}/weather/{weatherId}\n
    {regionId}: "long" // Идентификатор региона\n
    {weatherId}: "long" // Идентификатор погоды\n
    request { \n
        empty
    }\n
    response { \n
        "id": "long", // Идентификатор региона
        "name": "string", // Название региона
        "parentRegion": "string", // Название родительского региона
        "latitude": "double", // Координаты широты
        "longitude": double" // Координаты долготы
    }\n\n
    Условие - Статус
    1. Запрос успешно выполнен - 200
    2. regionId = null, regionId <= 0, weatherId = null, weatherId <= 0 - 400
    3. Неверные авторизационные данные - 401
    4. Регион с regionId не найден или погода с weatherId не найдена - 404
    """
    if regionId is None or regionId <= 0:
        raise HTTPException(detail={"message": "Missing region id"}, status_code=400)
    if weatherId is None or weatherId <= 0:
        raise HTTPException(detail={"message": "Missing weather id"}, status_code=400)
    region = Region.get_or_none(Region.id == regionId)
    if not region:
        raise HTTPException(detail={"message": "Region not found"}, status_code=404)
    weather = Weather.get_or_none(Weather.id == weatherId)
    if not weather:
        raise HTTPException(detail={"message": "Weather not found"}, status_code=404)
    weather.regionId = None
    weather.save()
    return RegionWeatherDisassignResponse.from_db(region)


# ping endpoint
@app.get("/")
@app.get("/ping")
async def ping(message: str = "Hello World"):
    return message

if __name__ == "__main__":
    # if (APP_DEBUG):
    #     uvicorn.run("main:app", host=APP_HOST, port=APP_PORT, reload=True, workers=1, root_path=ROOT_PATH)
    # else:
    uvicorn.run("main:app", host=APP_HOST, port=APP_PORT, reload=True, workers=3)
